/**
 * 矩阵上有`#`和`.`，等概率的选择一个`#`变为`.`，问`#`构成的连通块的期望是多少
 * 本质上就是问对每一个#，删除之后，会多出几个连通块，就是割点的问题
 * solution提供的解法本质上是一样的
 * 套模板即可
 */
#include <bits/stdc++.h>
using namespace std;

#include <bits/extc++.h>
using namespace __gnu_pbds;

#include <bits/stdc++.h>
using namespace std;

struct Graph{

using vi = vector<int>;
using vvi = vector<vi>;
using weight_t = int;
using edge_t = tuple<int, int, weight_t>;
using ve = vector<edge_t>;

vvi g;    // 邻接表，从1开始编号
ve edges; // 边表，从0开始编号

/// 如果是无向图，edgecnt需要等于题目给定边数的两倍
void init(int nodecnt, int edgecnt){
    g.assign(nodecnt + 1, vi());
    edges.clear();
    edges.reserve(edgecnt);
}
/// 单向边
void mkOneEdge(int a, int b, weight_t w){    
    g[a].emplace_back(edges.size());
    edges.emplace_back(a, b, w);
}
/// 双向边
void mkBiEdges(int a, int b, weight_t w){
    mkOneEdge(a, b, w);
    mkOneEdge(b, a, w);
}

int node_size() const {return g.size() - 1;}

const vi & operator [] (int pos) const {return g[pos];}

};

struct CutAndBridge{

const Graph & g;

int BridgeCnt;//桥的数量
vector<bool> IsCut;//点i是否为割点
vector<bool> IsBridge;//边i是否为桥
vector<int> AddBlocks;// ABi表示删除i点之后多出来的块数
int ConnCnt;

CutAndBridge() = delete;
CutAndBridge(const Graph & gg):g(gg){
    auto vn = g.node_size();
	auto en = g.edges.size();

	Dfn.assign(vn + 1, TimeStamp = BridgeCnt = 0);	
	Low.assign(vn + 1, 0);
	
	AddBlocks.assign(vn + 1, 0);
	IsCut.assign(vn + 1, false);

	IsBridge.assign(en, false);
	IsVisited.assign(en, false);    

    ConnCnt = 0;
	for(int i=1;i<=vn;++i)if(0==Dfn[i])++ConnCnt, dfs(i, i);
	return;
}

void dfs(int u, int pre){
	Dfn[u] = Low[u] = ++TimeStamp;

	int v, son = 0;
	for(auto i : g[u]){
		IsVisited[i] = IsVisited[i ^ 1] = true;
		const auto & e = g.edges[i];
        if(0 == Dfn[v = get<1>(e)]){
            ++son;
			dfs(v, u);
			if(Low[v] < Low[u]) Low[u] = Low[v];

			/// 这个if是桥
			if(Dfn[u] < Low[v]){
				IsBridge[i] = IsBridge[i ^ 1] = true;
				++BridgeCnt;
			}

			/// 这个if是割点
			if(u != pre && Dfn[u] <= Low[v]){
				IsCut[u] = true;
				++AddBlocks[u];
			}
		}else if(Dfn[v] < Low[u]){
            Low[u] = Dfn[v];
		}
	}

	/// 符合割点的条件1
	if(u == pre && son > 1) IsCut[u] = true;
	if(u == pre) AddBlocks[u] = son - 1;
}


int TimeStamp;
vector<int> Dfn, Low;
vector<bool> IsVisited; //边的标记数组


};

using llt = long long;
using pii = pair<int, int>;
using vi = vector<int>;
using vvi = vector<vi>;

llt const MOD = 998244353LL;
llt qpow(llt a, llt n){
    llt r = 1;
    while(n){
        if(n&1) r = r * a % MOD;
        a = a * a % MOD;
        n >>= 1;
    }
    return r;
}
llt inv(llt a){return qpow(a, MOD-2LL);}

int const DR[] = {-1, 1, 0, 0};
int const DC[] = {0, 0, -1, 1};

int H, W;
vector<string> Board;

int get(int r, int c){
    return r * W + c + 1;
}

Graph G;
llt proc(){
    int vn = H * W;
    int en = H * W * 4;
    int c = 0;
    G.init(vn, en);
    for(int i=0;i<H;++i)for(int j=0;j<W;++j){
        if('.' == Board[i][j]) continue;
        ++c;
        int u = get(i, j);
        if(i + 1 < H and Board[i + 1][j] == '#') G.mkBiEdges(u, get(i + 1, j), 0);
        if(j + 1 < W and Board[i][j + 1] == '#') G.mkBiEdges(u, get(i, j + 1), 0);
    }

    llt ans = 0;
    CutAndBridge cut(G);
    int total = cut.ConnCnt + c - vn;
    for(int i=0;i<H;++i)for(int j=0;j<W;++j){
        if('.' == Board[i][j]) continue;

        int u = get(i, j);
        int cha = cut.AddBlocks[u];
        int tmp = total + cha;
        ans = (ans + tmp) % MOD;
    }
    return ans * inv(c) % MOD;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("z.txt", "r", stdin);
#endif
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(0);
    int nofkase = 1;
    // cin >> nofkase;
    while(nofkase--){
        cin >> H >> W;
        Board.assign(H, "");
        for(auto & s : Board) cin >> s;
        cout << proc() << "\n";
    }
    return 0;
}